//! dfs inode 实现
//!
//! 使用该模块创建一个文件夹或者文件, 并提供对应的读写等操作

use std::{
    any::Any,
    collections::HashMap,
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc, LazyLock, Mutex,
    },
};

use chrono::{DateTime, Local};
#[cfg(feature = "command")]
use number_prefix::NumberPrefix;
use rcu::{call_rcu, RcuLock};

use crate::{
    filetable::{DfsFile, DfsMode},
    path::DfsPath,
    DSeekFrom, DfsError, DfsOpenOptions, DfsResult,
};

bitflags::bitflags! {
    /// inode 节点属性
    struct Iflags: u32 {
        // inode 可读
        const INODE_IS_READ   = 0x1;
        // inode 可以 seek
        const INODE_IS_SEEK   = 0x2;
        // inode 可以 fcntl
        const INODE_IS_FCNTL  = 0x4;
        // inode 是一个文件夹
        const INODE_IS_DIR    = 0x8;
        // inode 是一个文件
        const INODE_IS_FILE   = 0x10;
        // inode 是根节点
        const INODE_IS_ROOT   = 0x20;
    }
}

/// inode 节点数据
///
/// 包含节点名字, 属性, 子节点以及节点的私有数据
pub struct DfsInode {
    i_name: String,
    i_flags: Iflags,
    i_write: AtomicBool,
    i_childs: RcuLock<HashMap<String, Arc<DfsInode>>>,
    i_parent: Option<Arc<DfsInode>>,
    i_private: Arc<dyn InodeOperation>,
    i_mtime: Mutex<DateTime<Local>>,
    i_atime: Mutex<DateTime<Local>>,
}

unsafe impl Send for DfsInode {}
unsafe impl Sync for DfsInode {}

/// `InodeOperation` trait
///
/// 每个创建的节点私有数据实现, 包括打开/关闭文件,
/// 读些文件, seek/fcntl 文件.
#[allow(unused_variables)]
pub trait InodeOperation {
    /// 当一个节点打开时调用
    ///
    /// 如果实现该函数, 则需要返回本次 open 生命周期使用的私有数据
    ///
    /// # Errors
    /// 操作打开文件对象由文件返回结果
    fn open(&self) -> DfsResult<Box<dyn Any + Send>> {
        let empty = Box::new(());
        Ok(empty)
    }

    /// 当一个节点关闭时调用
    ///
    /// 可以在关闭节点时处理本次 open 生命周期结束后的清理工作,
    /// 如果只是为了释放数据是不需要 close 的, 但某些时候节点创建者会有额外 hook.
    fn close(&self, data: &mut Box<dyn Any + Send>) {}

    /// 从文件读取数据
    ///
    /// 节点创建者管理更新 pos, 并返回实际读取的数据量
    ///
    /// # Errors
    /// 操作读文件对象由文件返回结果
    fn read(
        &self,
        data: &mut Box<dyn Any + Send>,
        buf: &mut String,
        pos: &mut u64,
    ) -> DfsResult<usize> {
        Ok(0)
    }

    /// 向文件写入数据
    ///
    /// 节点创建者管理更新 pos, 并返回实际写入的数据量
    ///
    /// # Errors
    /// 操作写文件对象由文件返回结果
    fn write(&self, data: &mut Box<dyn Any + Send>, buf: &str, pos: &mut u64) -> DfsResult<usize> {
        Ok(0)
    }

    /// 调整文件指针位置
    ///
    /// 节点创建者管理更新 `raw_pos`, 并返回更新后的 pos
    ///
    /// # Errors
    /// 操作 seek 文件对象由文件返回结果
    fn seek(
        &self,
        data: &mut Box<dyn Any + Send>,
        pos: DSeekFrom,
        raw_pos: &mut u64,
    ) -> DfsResult<u64> {
        let max_size = self.size() as u64;
        let new_pos = match pos {
            DSeekFrom::Start(s) => s,
            DSeekFrom::Current(cur) => raw_pos.wrapping_add_signed(cur),
            DSeekFrom::End(end) => max_size.wrapping_add_signed(end),
        };

        if new_pos > max_size {
            return Err(DfsError::IllegalSeek);
        }
        *raw_pos = new_pos;
        Ok(new_pos)
    }

    /// 文件的控制操作
    ///
    /// cmd 可以由节点自定义实现
    ///
    /// # Errors
    /// 操作 fcntl 文件对象由文件返回结果
    fn fcntl(&self, cmd: u32, data: &mut Box<dyn Any>) -> DfsResult<()> {
        Err(DfsError::IllegalFcntl)
    }

    /// 文件大小
    ///
    /// 获取文件大小
    fn size(&self) -> usize;
}

/// 文件创建选项
#[derive(Debug, Clone, Default)]
pub struct FileCreateOptions {
    read: bool,
    write: bool,
    seek: bool,
    fcntl: bool,
    name: String,
}

impl FileCreateOptions {
    /// 创建一个新的 `FileCreateOptions`
    pub fn new() -> Self {
        Self::default()
    }

    /// 节点是否可读
    pub fn read(&mut self, read: bool) -> &mut Self {
        self.read = read;
        self
    }

    /// 节点是否可写
    pub fn write(&mut self, write: bool) -> &mut Self {
        self.write = write;
        self
    }

    /// 节点是否可读可写
    pub fn read_write(&mut self, rdwr: bool) -> &mut Self {
        self.read(rdwr);
        self.write(rdwr);
        self
    }

    /// 节点是否可以 seek
    pub fn seek(&mut self, seek: bool) -> &mut Self {
        self.seek = seek;
        self
    }

    /// 节点是否可以 fcntl
    pub fn fcntl(&mut self, fcntl: bool) -> &mut Self {
        self.fcntl = fcntl;
        self
    }

    /// 节点名字
    pub fn name(&mut self, name: &str) -> &mut Self {
        self.name = name.into();
        self
    }

    /// 创建节点文件
    ///
    /// # Errors
    /// 节点为文件, 创建属性, 格式不匹配等将返回错误
    pub fn create_file(
        &self,
        inode: Arc<DfsInode>,
        handler: Arc<dyn InodeOperation>,
    ) -> DfsResult<Arc<DfsInode>> {
        self.create(inode, handler, true)
    }

    fn get_iflags(&self, file: bool) -> Iflags {
        let mut flags = Iflags::empty();

        if self.read {
            flags |= Iflags::INODE_IS_READ;
        }
        if self.seek {
            flags |= Iflags::INODE_IS_SEEK;
        }
        if self.fcntl {
            flags |= Iflags::INODE_IS_FCNTL;
        }
        if file {
            flags |= Iflags::INODE_IS_FILE;
        } else {
            flags |= Iflags::INODE_IS_DIR;
        }
        flags
    }

    fn create(
        &self,
        inode: Arc<DfsInode>,
        handler: Arc<dyn InodeOperation>,
        file: bool,
    ) -> DfsResult<Arc<DfsInode>> {
        if self.name.is_empty() || self.name.contains('/') {
            return Err(DfsError::InvalidArgs);
        }
        if inode.is_file() {
            return Err(DfsError::PermOperation);
        }
        if self.read && !inode.readable() || self.write && !inode.writeable() {
            return Err(DfsError::PermOperation);
        }

        let flags = self.get_iflags(file);
        let new = Arc::new(DfsInode {
            i_name: self.name.clone(),
            i_flags: flags,
            i_write: AtomicBool::new(self.write),
            i_childs: RcuLock::new(HashMap::new()),
            i_parent: Some(inode.clone()),
            i_private: handler,
            i_mtime: Mutex::new(Local::now()),
            i_atime: Mutex::new(Local::now()),
        });

        let mut lock = inode.i_childs.write().unwrap();
        if lock.contains_key(&self.name) {
            if file {
                return Err(DfsError::ExistFile);
            } else {
                return Err(DfsError::ExistDir);
            }
        }
        lock.insert(self.name.clone(), new.clone());
        Ok(new)
    }
}

/// 目录创建选项
#[derive(Debug, Clone, Default)]
pub struct DirCreateOptions {
    read: bool,
    write: bool,
    name: String,
}

impl DirCreateOptions {
    /// 创建一个新的 `DirCreateOptions`
    pub fn new() -> Self {
        Self::default()
    }

    /// 节点是否可读
    pub fn read(&mut self, read: bool) -> &mut Self {
        self.read = read;
        self
    }

    /// 节点是否可写
    pub fn write(&mut self, write: bool) -> &mut Self {
        self.write = write;
        self
    }

    /// 节点是否可读可写
    pub fn read_write(&mut self, rdwr: bool) -> &mut Self {
        self.read(rdwr);
        self.write(rdwr);
        self
    }

    /// 目录名字
    pub fn name(&mut self, name: &str) -> &mut Self {
        self.name = name.into();
        self
    }

    /// 创建节点文件夹
    ///
    /// # Errors
    /// 节点为文件, 创建属性, 格式不匹配等将返回错误
    pub fn create_dir(&self, inode: Arc<DfsInode>) -> DfsResult<Arc<DfsInode>> {
        let handler = Arc::new(InodeOperationDefault::new());
        FileCreateOptions::new()
            .name(&self.name)
            .read(self.read)
            .write(self.write)
            .create(inode, handler, false)
    }
}

struct InodeOperationDefault;

impl InodeOperationDefault {
    fn new() -> Self {
        Self
    }
}

impl InodeOperation for InodeOperationDefault {
    fn open(&self) -> DfsResult<Box<dyn Any + Send>> {
        Err(DfsError::IsDir)
    }

    fn read(&self, _: &mut Box<dyn Any + Send>, _: &mut String, _: &mut u64) -> DfsResult<usize> {
        Err(DfsError::IsDir)
    }

    fn write(&self, _: &mut Box<dyn Any + Send>, _: &str, _: &mut u64) -> DfsResult<usize> {
        Err(DfsError::IsDir)
    }

    fn seek(&self, _: &mut Box<dyn Any + Send>, _: DSeekFrom, _: &mut u64) -> DfsResult<u64> {
        Err(DfsError::IsDir)
    }

    fn fcntl(&self, _: u32, _: &mut Box<dyn Any>) -> DfsResult<()> {
        Err(DfsError::IsDir)
    }

    fn size(&self) -> usize {
        0
    }
}

static INODE_ROOT: LazyLock<Arc<DfsInode>> = LazyLock::new(|| {
    let handler = InodeOperationDefault::new();
    let handle = Arc::new(handler);

    Arc::new(DfsInode {
        i_name: String::from("/"),
        i_flags: Iflags::INODE_IS_READ | Iflags::INODE_IS_DIR | Iflags::INODE_IS_ROOT,
        i_write: AtomicBool::new(true),
        i_childs: RcuLock::new(HashMap::new()),
        i_parent: None,
        i_private: handle,
        i_mtime: Mutex::new(Local::now()),
        i_atime: Mutex::new(Local::now()),
    })
});

/// 根节点
pub fn inode_root() -> Arc<DfsInode> {
    INODE_ROOT.clone()
}

impl DfsInode {
    /// 节点名字
    #[inline]
    pub fn name(&self) -> &String {
        &self.i_name
    }

    #[inline]
    fn is_contains(&self, i_flags: Iflags) -> bool {
        self.i_flags.contains(i_flags)
    }

    /// 节点是否可读
    #[inline]
    pub fn readable(&self) -> bool {
        self.is_contains(Iflags::INODE_IS_READ)
    }

    /// 节点是否可写
    #[inline]
    pub fn writeable(&self) -> bool {
        self.i_write.load(Ordering::Relaxed)
    }

    /// 节点是否可以 seek
    #[inline]
    pub fn seekable(&self) -> bool {
        self.is_contains(Iflags::INODE_IS_SEEK)
    }

    /// 节点是否可以 fcntl
    #[inline]
    pub fn fcntlable(&self) -> bool {
        self.is_contains(Iflags::INODE_IS_FCNTL)
    }

    /// 节点是文件
    #[inline]
    pub fn is_file(&self) -> bool {
        self.is_contains(Iflags::INODE_IS_FILE)
    }

    /// 节点是文件夹
    #[inline]
    pub fn is_dir(&self) -> bool {
        self.is_contains(Iflags::INODE_IS_DIR)
    }

    /// 节点是根节点
    #[inline]
    pub fn is_root(&self) -> bool {
        debug_assert!(self.i_parent.is_none());
        self.is_contains(Iflags::INODE_IS_ROOT)
    }

    /// 节点父亲
    #[inline]
    pub fn parent(&self) -> Option<Arc<DfsInode>> {
        self.i_parent.clone()
    }

    /// 移除文件
    ///
    /// # Errors
    /// 文件不存在, 文件被多次引用将会返回错误
    #[allow(clippy::missing_panics_doc)]
    pub fn remove_file(&self, name: &str) -> DfsResult<()> {
        if self.is_file() {
            return Err(DfsError::IsFile);
        }

        let mut lock = self.i_childs.write().unwrap();

        let data = lock.get(name);
        if let Some(d) = data {
            if d.is_dir() {
                return Err(DfsError::IsDir);
            }
            if Arc::strong_count(d) != 1 {
                debug_assert!(d.i_childs.read().unwrap().is_empty());
                return Err(DfsError::BusyInode);
            }
        } else {
            return Err(DfsError::NoFile);
        }
        let data = lock.remove(name).unwrap();
        call_rcu(move || drop(data));
        Ok(())
    }

    /// 移除文件夹
    ///
    /// # Errors
    /// 文件夹不存在, 文件夹被多次引用将会返回错误
    #[allow(clippy::missing_panics_doc)]
    pub fn remove_dir(&self, name: &str) -> DfsResult<()> {
        if self.is_file() {
            return Err(DfsError::IsFile);
        }

        let mut lock = self.i_childs.write().unwrap();

        let data = lock.get(name);
        if let Some(d) = data {
            if d.is_file() {
                return Err(DfsError::IsFile);
            }
            if Arc::strong_count(d) != 1 {
                return Err(DfsError::BusyInode);
            }
            if !d.i_childs.read().unwrap().is_empty() {
                return Err(DfsError::DirNoEmpty);
            }
        } else {
            return Err(DfsError::NoDir);
        }

        let data = lock.remove(name).unwrap();
        call_rcu(move || drop(data));
        Ok(())
    }

    /// 遍历节点
    #[allow(clippy::missing_panics_doc)]
    pub fn for_each_childs<F: FnMut(&Arc<DfsInode>)>(&self, mut f: F) {
        let read = self.i_childs.read().unwrap();
        for inode in read.iter() {
            f(inode.1);
        }
    }

    /// 标记节点(file)不可写
    ///
    /// 该功能用于某些特殊文件, 为它们提供一个标记为不可写文件的可能, 避免后续操作访问修改它们
    ///
    /// # Errors
    /// 如果操作节点不是文件夹, 文件夹下没有该文件, 或者文件正被打开, 那么修改属性会失败
    #[allow(clippy::missing_panics_doc)]
    pub fn set_no_write(&self, name: &str) -> DfsResult<()> {
        if self.is_file() {
            return Err(DfsError::IsFile);
        }

        let lock = self.i_childs.read().unwrap();

        let data = lock.get(name);
        if let Some(d) = data {
            if d.is_dir() {
                return Err(DfsError::IsDir);
            }
            if Arc::strong_count(d) != 1 {
                debug_assert!(d.i_childs.read().unwrap().is_empty());
                return Err(DfsError::BusyInode);
            }

            d.i_write.store(false, Ordering::Relaxed);
        } else {
            return Err(DfsError::NoFile);
        }
        Ok(())
    }
}

pub(crate) fn open(inode: Arc<DfsInode>, options: &DfsOpenOptions) -> DfsResult<DfsFile> {
    if inode.is_dir() {
        return Err(DfsError::IsDir);
    }

    let mut mode = DfsMode::empty();
    if options.read {
        if !inode.readable() {
            return Err(DfsError::NoRead);
        }
        mode.set(DfsMode::READ, true);
    }

    if options.write {
        if !inode.writeable() {
            return Err(DfsError::NoWrite);
        }
        mode.set(DfsMode::WRITE, true);
    }

    if options.seek {
        if !inode.seekable() {
            return Err(DfsError::NoSeek);
        }
        mode.set(DfsMode::SEEK, true);
    }

    if options.fcntl {
        if !inode.fcntlable() {
            return Err(DfsError::NoFcntl);
        }
        mode.set(DfsMode::FCNTL, true);
    }

    if options.noblock {
        mode.set(DfsMode::NOBLOCK, true);
    }

    let data = inode.i_private.open()?;
    let file = DfsFile::new(data, inode.clone(), mode);
    Ok(file)
}

pub(crate) fn close(inode: Arc<DfsInode>, file: &mut DfsFile) -> DfsResult<()> {
    if inode.is_dir() {
        return Err(DfsError::IsDir);
    }

    inode.i_private.close(file.private.get_mut());
    Ok(())
}

pub(crate) fn read(file: &mut DfsFile, buf: &mut String) -> DfsResult<usize> {
    if !file.mode.contains(DfsMode::READ) {
        return Err(DfsError::PermOperation);
    }
    debug_assert!(file.inode.readable());

    let mut pos = file.pos;
    let size = file.inode.i_private.read(file.private.get_mut(), buf, &mut pos)?;
    let mut lock = file.inode.i_atime.lock().unwrap();
    *lock = Local::now();
    file.pos = pos;
    Ok(size)
}

pub(crate) fn write(file: &mut DfsFile, buf: &str) -> DfsResult<usize> {
    if !file.mode.contains(DfsMode::WRITE) {
        return Err(DfsError::PermOperation);
    }
    debug_assert!(file.inode.writeable());

    let mut pos = file.pos;
    let size = file.inode.i_private.write(file.private.get_mut(), buf, &mut pos)?;
    let mut lock = file.inode.i_mtime.lock().unwrap();
    *lock = Local::now();
    file.pos = pos;
    Ok(size)
}

pub(crate) fn seek(file: &mut DfsFile, pos: DSeekFrom) -> DfsResult<u64> {
    if !file.mode.contains(DfsMode::SEEK) {
        return Err(DfsError::PermOperation);
    }
    debug_assert!(file.inode.seekable());

    let mut raw_pos = file.pos;
    let size = file.inode.i_private.seek(file.private.get_mut(), pos, &mut raw_pos)?;
    file.pos = raw_pos;
    Ok(size)
}

pub(crate) fn fcntl(file: &mut DfsFile, cmd: u32, data: &mut Box<dyn Any>) -> DfsResult<()> {
    if !file.mode.contains(DfsMode::FCNTL) {
        return Err(DfsError::PermOperation);
    }
    debug_assert!(file.inode.fcntlable());

    file.inode.i_private.fcntl(cmd, data)
}

static CURRENT_PATH: LazyLock<RcuLock<Arc<DfsInode>>> =
    LazyLock::new(|| RcuLock::new(inode_root()));

/// 更新当前工作路径节点
///
/// # Errors
/// 节点是文件将会返回错误
#[allow(clippy::missing_panics_doc)]
pub fn set_current_path(inode: Arc<DfsInode>) -> DfsResult<()> {
    if inode.is_file() {
        return Err(DfsError::IsFile);
    }
    let mut current = CURRENT_PATH.write().unwrap();
    *current = inode;
    Ok(())
}

fn update_prev(start: &Arc<DfsInode>) -> Arc<DfsInode> {
    if start.parent().is_none() { start.clone() } else { start.parent().unwrap() }
}

/// 获取路径对应的节点
#[allow(clippy::missing_panics_doc)]
pub fn link_path_walk(path: &str) -> Option<Arc<DfsInode>> {
    let dfs_path = DfsPath::new(path);
    let mut start =
        if dfs_path.is_absolute() { inode_root() } else { CURRENT_PATH.read().unwrap().clone() };
    let slice = dfs_path.to_slice();

    if dfs_path.len() == 1 && dfs_path.is_absolute() {
        return Some(inode_root());
    }
    if dfs_path.is_empty() {
        return None;
    }

    let mut next;
    let mut prev = update_prev(&start);
    for name in slice {
        // . 和 .. 为特殊情况, 单独处理
        if name == "." {
            continue;
        }
        if name == ".." {
            start = prev.clone();
            continue;
        }
        next = None;
        let childs = start.i_childs.read().unwrap();
        for (str, inode) in childs.iter() {
            if str == name.as_str() {
                next = Some(inode.clone());
                prev = update_prev(&start);
                break;
            }
        }
        match next {
            Some(n) => start = n,
            None => return None,
        }
    }
    Some(start)
}

//  attr1   attr2   attr3   attr4
// [drwsf] [mtime] [atime] [filename]
#[cfg(feature = "command")]
pub(crate) fn inode_file_attr(inode: Arc<DfsInode>) -> String {
    let mut attr = String::new();
    let fmt = "%m月%d日%H:%M:%S";
    let atime;
    {
        let lock = inode.i_atime.lock().unwrap();
        atime = lock.format(fmt).to_string();
    }
    let mtime;
    {
        let lock = inode.i_mtime.lock().unwrap();
        mtime = lock.format(fmt).to_string();
    }
    let no = "-";
    let read = if inode.readable() { "r" } else { no };
    let write = if inode.writeable() { "w" } else { no };
    let seek = if inode.seekable() { "s" } else { no };
    let fcntl = if inode.fcntlable() { "f" } else { no };
    let dir = if inode.is_dir() { "d" } else { no };

    let attr4 = match NumberPrefix::decimal(inode.i_private.size() as f64) {
        NumberPrefix::Standalone(bytes) => {
            format!("{:>6} ", bytes)
        },
        NumberPrefix::Prefixed(prefix, n) => {
            format!("{:>4.1}{}B ", n, prefix)
        },
    };

    let attr1 = format!("{}{}{}{}{} ", dir, read, write, seek, fcntl);
    let attr2 = format!("{} ", mtime);
    let attr3 = format!("{} ", atime);
    let attr5 = inode.name().to_string();
    attr.push_str(&attr1);
    attr.push_str(&attr2);
    attr.push_str(&attr3);
    attr.push_str(&attr4);
    attr.push_str(&attr5);
    attr
}
